<?php

/**
 * Класс для кэширования объектов и константа, идентифицирующая отсутствие кэшированного объекта в постоянном кэше.
 *
 * @package mumak
 * @subpackage core
 * @author Алексей Михайлов <amikhailov83@mail.ru>
 * @version $Id: Cache.php 62 2007-09-05 08:27:21Z lucky $
 */

/**
 * Показывает, что кэш отсутствует на диске/memcached
 */
define("CACHE_DUMP_NOT_EXISTS", -984);
/**
 * Драйвер кэширования - memcache
 */
define("CACHE_DRIVER_MEMCACHE" , 1);
/**
 * Драйвер кэширование - файлы на диске
 */
define("CACHE_DRIVER_FILE" , 0);

/**
 * RL_SiteRequestHandler class
 *
 * @package mumak
 * @subpackage core
 */
class RL_Cache {

	/**
	 * Ссылка на RH-объект
	 *
	 * @var RequestHandler
	 * @access public
	 */
	public $rh;

	/**
	 * Массив с данными
	 *
	 * @var array
	 * @access private
	 */
	private $data;

	/**
	 * Уровни данных
	 *
	 * @var array
	 * @access private
	 */
	private $levels;

	/**
	 * Ссылка на объект debug
	 *
	 * @var Debug
	 */
	private $debug;

	/**
	 * Memcached server
	 *
	 * @var Memcache
	 */
	private $memcache;

	/**
	 * Соединились ли с memcache
	 *
	 * @var bool
	 */
	private $memcache_connected;

	/**
	 * Текущий драйвер кэширования
	 *
	 * @var int
	 */
	private $current_driver = CACHE_DRIVER_MEMCACHE;

	/**
	 * Последний драйвер
	 *
	 * @var int
	 */
	private $last_driver = NULL;

	/**
	 * Конструктор объекта
	 *
	 * @param RL_SiteRequestHandler $rh  ссылка на RequestHandler, в котором содержится конфигурация проекта
	 * @return RL_Cache
	 * @access public
	 */
	public function __construct( RL_Context $ctx ) {
		$this->data = array();
		$this->rh = $ctx->rh;
		$this->debug = $ctx->debug;

		$this->current_driver = CACHE_DRIVER_FILE;

		if(isset($this->rh->use_memcache) && $this->rh->use_memcache) {
			if(class_exists("Memcache")) {
				$this->memcache = new Memcache;
				$this->memcache_connected = $this->memcache->pconnect( $this->rh->memcache_host , $this->rh->memcache_port );

				if($this->memcache_connected) {
					$this->debug->trace("Connected to MEMCACHED. Server version: " . $this->memcache->getVersion() . "; Server statistics: " . $this->debug->trace_r( $this->memcache->getStats() , 1));

					if(isset($_GET["flush_cache"]) && $_GET["flush_cache"] == 1) {
						$this->debug->trace("Flush MEMCACHE");
						$this->memcache->flush();
					}

					$this->current_driver = CACHE_DRIVER_MEMCACHE;
				} else {
					$this->debug->error("Cannot connect to MEMCACHED");
				}
			} else {
				$this->debug->error("Class Memcache not defined. Update your PHP.");
			}
		}
	}

	/**
	 * Устанавливает драйвер постоянного кэша
	 *
	 * @param int $driver_type
	 * @return void
	 */
	public function setDriver( $driver_type ) {
		$this->last_driver = $this->current_driver;
		$this->current_driver = $driver_type;
	}

	/**
	 * Возвращается к предыдущему состоянию
	 *
	 * @return void
	 */
	public function restoreDriver() {
		if($this->last_driver == NULL) {
			return;
		} else {
			$this->current_driver = $this->last_driver;
			$this->last_driver = NULL;
		}
	}

	/**
	 * Возвращает ссылку на объект из кэша
	 *
	 * @param string $object_id  ID объекта
	 * @return ссылка на объект кэша, либо false, если его там нет
	 * @access public
	 */
	public function restore( $object_id ) {
		if (isset($this->data[$object_id])) {
			return $this->data[$object_id];
		} else {
			return false;
		}
	}

	/**
	 * Сохраняет объект в кэш
	 *
	 * @param string $object_id  ID объекта
	 * @param mixed $object  ссылка на сохраняемый объект
	 * @return void
	 * @access public
	 */
	public function store( $object_id, $object ) {
		$this->data[$object_id] = $object;
	}

	/**
	 * очистить кэш от объекта/-ов
	 *
	 * @param string $object_id
	 *
	 * @return void
	 * @access public
	 */
	public function clear( $object_id = "" ) {
		if ($object_id) {
			unset($this->data[$object_id]);
		} else {
			$this->data = array();
		}
	}

	/**
	 * Сохраняет кэшируемый объект в файл на диске, либо в memcached
	 *
	 * @param string $object_id
	 * @param mixed $object
	 * @param int $ttl
	 * @return bool
	 */
	public function dump ( $object_id , $object , $ttl = 0) {
		$key = $this->_getUID( $object_id );
		// сначала попытаемся запихнуть в memcached
		if($this->memcache_connected && $this->current_driver == CACHE_DRIVER_MEMCACHE && $this->rh->use_memcache ) {
			$result = $this->memcache->set($key , $object , 0 , $ttl);
			if($result) {
				$this->debug->trace("Object \"$object_id\" dumped to MEMCACHE" );
				return true;
			}
		}
		// имя файла
		$filename = $key . ".dat";
		$filename = $this->rh->cache_dir . $filename;
		if($fp = fopen($filename, "wb")) {
			fwrite($fp, serialize($object));
			fclose($fp);
			$this->rh->debug->out("Object \"$object_id\" succesfully dumped to cache (CacheID: " . $this->_getUID( $object_id ) . ")" , "CACHE");
			return true;
		} else {
			$this->rh->debug->error("Failed to dump object \"$object_id\" to cache");
			return false;
		}
	}

	/**
	 * Получает объект из кэша на диске, либо из memcached
	 *
	 * @param string $object_class
	 * @param string $object_id
	 * @param int $ttl  время в секундах - "срок годности" кэша; 0 - кэш бесконечен
	 * @return mixed
	 */
	public function fromDump ( $object_id , $ttl = 0 ) {
		$key = $this->_getUID( $object_id );
		// сначала попытаемся взять из memcached
		if($this->memcache_connected && $this->current_driver == CACHE_DRIVER_MEMCACHE && $this->rh->use_memcache ) {
			$result = $this->memcache->get($key);
			if($result !== false) {
				$this->debug->out("Object \"$object_id\" dumped from MEMCACHE" , "CACHE");
				return $result;
			}
		}
		// имя файла
		$filename = $key . ".dat";
		$filename = $this->rh->cache_dir . $filename;
		if(file_exists($filename)) {
			if($ttl == 0 || filemtime($filename) >= (time() - $ttl)) {
				$object = @file_get_contents($filename);
				$this->rh->debug->out("Object \"$object_id\" dumped from cache (CacheID: " . $this->_getUID( $object_id ) . ")" , "CACHE");
				return unserialize($object);
			} else {
				$this->rh->debug->out("Object \"$object_id\" exists in disc cache, but its too old" , "CACHE");
				return CACHE_DUMP_NOT_EXISTS;
			}
		} else {
			$this->rh->debug->error("Cannot read object dump \"$object_id\" from cache");
			return CACHE_DUMP_NOT_EXISTS;
		}
	}

	/**
	 * Удаляет объект из дискового кэша, либо из memcached
	 *
	 * @param string $object_class
	 * @param string $object_id
	 * @return bool
	 */
	public function clearDump ( $object_id ) {
		$key = $this->_getUID( $object_id );
		if($this->memcache_connected && $this->current_driver == CACHE_DRIVER_MEMCACHE && $this->rh->use_memcache ) {
			$this->memcache->delete($key);
		}
		// имя файла
		$filename = $key . ".dat";
		$filename = $this->rh->cache_dir . $filename;
		if(file_exists($filename)) {
			$this->rh->debug->out("Object \"$object_id\" succesfully cleaned from disc cache" , "CACHE");
			return @unlink($filename);
		} else {
			$this->rh->debug->error("Object \"$object_id\" doesn't exists in cache. Cleaning failed.");
		}
	}

	/**
	 * Получает время последнего занесения объекта в файловый кэш
	 *
	 * @param string $object_class
	 * @param string $object_id
	 * @return int timestamp
	 */
	public function getCachedTime ( $object_id ) {
		$key = $this->_getUID( $object_id );
		if($this->current_driver == CACHE_DRIVER_MEMCACHE) {
			return false;
		}

		$filename = $key . ".dat";
		$filename = $this->rh->cache_dir . $filename;

		if(file_exists($filename)) {
			return filemtime( $filename );
		} else {
			return false;
		}
	}

	/**
	 * Получает уникальный идентификатор объекта в кэше, исходя из его класса, ID и уровня кэширования.
	 *
	 * @param string $object_class  класс объекта
	 * @param string $object_id  ID объекта
	 * @param int $cache_level  уровень кэширования
	 *
	 * @return string  MD5-хэшированный возвращает уникальный идентификатор-строку (напр. md5("db/results/SELECT * FROM t1") )
	 * @access private
	 */
	private function _getUID ( $object_id ) {
		return md5( $object_id );
	}

}

